﻿using System;
using System.Collections.Generic;
using iTextSharp.text;
using iTextSharp.text.pdf;
using System.Text;
using System.Linq;
using System.IO;
using Ch.Admin.Bfs.Sms.Sdmx.Extension.CodeBook.Meta;
using Ch.Admin.Bfs.Sms.Sdmx.Extension.CodeBook.Configuration;

namespace Ch.Admin.Bfs.Sms.Sdmx.Extension.CodeBook.Pdf
{

    #region Margin Structure

    public class DiamMargin
    {
        public float Top;
        public float Bottom;
        public float Left;
        public float Right;

        public void SwitchMargin()
        {
            float temp = Left;
            Left = Right;
            Right = temp;
        }

        public DiamMargin(Document doc)
        {
            Top = doc.TopMargin;
            Bottom = doc.BottomMargin;
            Left = doc.LeftMargin;
            Right = doc.RightMargin;
        }
    }

    #endregion

    public class BookGeneratorB
    {
        /// <summary>ITextSharp objects for Pdf document generation </summary>
        private PdfWriter docWriter;
        private Document doc;
        public PdfFonts Fonts;
        private string lg;
        private IMetaProviderFactory metaProviderFactory;
        private BookConfiguration bookConfig;
        private TableOfContentModule tocModule;
        private IMetaProvider ImpressumProvider;
        private ImpressumModule impressumModule;

        private DiamMargin Margins;

        List<TableOfContentsEntry> contentsTable;

        private List<PdfBookPTable> BookTables;

        public BookGeneratorB(string fontDirectory, IMetaProviderFactory metaFactory)
        {
            // Embedding font 
            string fontDir = fontDirectory;
            Fonts = new PdfFonts(fontDir);

            // Metadata provider
            metaProviderFactory = metaFactory;
            BookTables = new List<PdfBookPTable>();
        }

        protected float TotalHeight
        {
            get
            {
                return doc.PageSize.Height - bookConfig.Margins.Top - bookConfig.Margins.Bottom;
            }
        }
        public float TotalWidth
        {
            get
            {
                return doc.PageSize.Width - doc.LeftMargin - doc.RightMargin;
            }
        }

        #region New Way Generation

        public void Generate(string pdfPath, BookHierarchy bookHierarchy, string lang, BookConfiguration cfg, System.Drawing.Image logo)
        {
            lg = lang;
            bookConfig = cfg;
            bookConfig.LanguageId = lang;


            // instanciate document
            BookTables.Clear();
            doc = new Document(PageSize.A4, bookConfig.Margins.Left, bookConfig.Margins.Right,
                bookConfig.Margins.Top, bookConfig.Margins.Bottom);
            Margins = new DiamMargin(doc);

            try
            {

                // Clear history of used codelist
                metaProviderFactory.ClearReferedCodelist();

                // Instantiate Pdf doc writer
                docWriter = PdfWriter.GetInstance(doc, new FileStream(pdfPath, FileMode.Create));
                //    docWriter.SetLinearPageMode();

                doc.Open();

                // Creates Pdf Tables for each modules types, Construct Toc entries
                contentsTable = new List<TableOfContentsEntry>();

                foreach (HierarchyNode nodeModuleType in bookHierarchy.Nodes)
                {
                    TableOfContents moduleType = (TableOfContents)((int)nodeModuleType.Tag);

                    // Codelist always placed at end on module documents
                    if (moduleType != TableOfContents.Codelists)
                        GenerateModulesOfContent(moduleType, nodeModuleType.Nodes);
                }

                // Now detected codelist merged with specified ones
                HierarchyNode nodeReferedCodelists = SpecificMergeCodelists(bookHierarchy);
                if (nodeReferedCodelists.Nodes.Count > 0)
                {
                    // Generate a common title for codelists
                    PropertyTable pt = new PropertyTable(null, Fonts, doc);
                    pt.AddMainTitle(bookConfig.TitleModuleCodelist[lg]);
                    pt.AddLeading(15f);
                    BookTables.Add(new PdfBookPTable(pt.Table, true));

                    GenerateModulesOfContent(TableOfContents.Codelists, nodeReferedCodelists.Nodes);
                }

                // Cover Page
                GenerateCover(logo);
                // Impressum Page
                Margins.SwitchMargin();

                if (ImpressumProvider != null)
                {
                    impressumModule = new ImpressumModule(ImpressumProvider, doc, Fonts, bookConfig);
                    PdfPTable tbImpressum = impressumModule.Tables[0].Table;
                    float height = tbImpressum.CalculateHeights(true);
                    tbImpressum.WriteSelectedRows(0, -1, Margins.Left, Margins.Bottom + height, docWriter.DirectContent);

                    doc.NewPage();
                    Margins.SwitchMargin();
                }

                // Calculates pafes and generate Table of entries
                int page = 1;
                CreateTableEntry(BookTables, ref page);

                tocModule = new TableOfContentModule(bookConfig.TableOfContentName[lg], null, doc, Fonts, bookConfig);
                tocModule.SetTocContent(contentsTable);

                float pos = TotalHeight + bookConfig.Margins.Top / 2;
                tocModule.Tables[0].Table.WriteSelectedRows(0, -1, Margins.Left, pos, docWriter.DirectContent);
                pos -= tocModule.Tables[0].Table.CalculateHeights(true) + 30f;
                tocModule.Tables[1].Table.WriteSelectedRows(0, -1, Margins.Left, pos, docWriter.DirectContent);
                doc.NewPage();
                Margins.SwitchMargin();

                //Write Modules pages
                page = 1;
                DistributeModule(BookTables, ref page);

            }
            catch (Exception ex)
            {
                throw ex;
            }
            finally
            {
                try
                {
                    if (doc != null && doc.IsOpen())
                        doc.Close();
                }
                catch { }
            }
        }
        #endregion

        #region Cover Page

        private void GenerateCover(System.Drawing.Image logo)
        {
            BaseModule bm = new BaseModule(null, doc, Fonts, bookConfig);
            PdfContentByte cb = docWriter.DirectContent; 
           // Do we have a defined image
            if (bookConfig.GetLogo(this.lg).Length > 0)
            {
                iTextSharp.text.Image jpg = iTextSharp.text.Image.GetInstance(bookConfig.GetLogo(this.lg));

                //Resize image depend upon your need
                jpg.ScaleToFit(doc.PageSize.Width, 120f);

                jpg.SetAbsolutePosition(0f, doc.PageSize.Height - 120f);
                cb.AddImage(jpg);


            }

            // Cover Sub Title & title
            PdfPTable table = new PdfPTable(1);
            table.TotalWidth = bm.TotalWidth;

            Phrase ph = new Phrase(bookConfig.CoverSubTitle[bookConfig.LanguageId], Fonts.FontCoverSubTitle);
            PdfPCell cell = new PdfPCell(ph);
            cell.Border = Rectangle.NO_BORDER;
            cell.VerticalAlignment = Element.ALIGN_MIDDLE;
            cell.MinimumHeight = 40f;
            table.AddCell(cell);

            ph = new Phrase(bookConfig.CoverTitle[bookConfig.LanguageId], Fonts.FontCoverTitle);
            cell = new PdfPCell(ph);
            cell.Border = Rectangle.BOTTOM_BORDER | Rectangle.TOP_BORDER;
            cell.VerticalAlignment = Element.ALIGN_MIDDLE;
            cell.MinimumHeight = 100f;

            table.AddCell(cell);
            ph = new Phrase("Codebook", Fonts.FontCoverSubTitle);
            cell = new PdfPCell(ph);
            cell.Border = Rectangle.NO_BORDER;
            cell.VerticalAlignment = Element.ALIGN_MIDDLE;
            cell.MinimumHeight = 40f;

            table.AddCell(cell);
           
            table.WriteSelectedRows(0, -1, Margins.Left, doc.PageSize.Height - doc.PageSize.Height / 3, docWriter.DirectContent);

            doc.NewPage();
        }
        #endregion

        #region Codelist merging
        /// <summary>
        /// Merge detected codelist with user specific list that could be provided
        /// </summary>
        /// <param name="bookHierarchy">List of all artefacts specified, grouped by domain type</param>
        /// <returns>a Node with subNodes holding codelist id's</returns>
        private HierarchyNode SpecificMergeCodelists(BookHierarchy bookHierarchy)
        {
            // Detected codelist (if requested)
            HierarchyNode nodeReferedCodelists = metaProviderFactory.ReferedCodelistModuleContent();

            // Now add specifically selected if not yet part of list
            foreach (HierarchyNode nodeModuleType in bookHierarchy.Nodes)
            {
                // Select codelist domain
                if ((TableOfContents)((int)nodeModuleType.Tag) == TableOfContents.Codelists)
                {
                    foreach (HierarchyNode clNode in nodeModuleType.Nodes)
                    {
                        HierarchyNode nd = nodeReferedCodelists.Nodes.Find(x => x.Id == clNode.Id);
                        if (nd == null)
                        {
                            nodeReferedCodelists.Nodes.Add(new HierarchyNode(clNode.Id, string.Empty, null));
                        }
                    }
                }
            }
            return nodeReferedCodelists;
        }
        #endregion

        private void GenerateModulesOfContent(TableOfContents moduleType, List<HierarchyNode> fullIdList)
        {
            foreach (HierarchyNode nodeModule in fullIdList)
                GenerateModule(moduleType, nodeModule.Id);
        }

        private void GenerateModule(TableOfContents moduleType, string metafullId)
        {
            IMetaProvider metaProvider = metaProviderFactory.GetMetaProvider(moduleType, lg, metafullId, this.bookConfig);
            if (metaProvider == null)
                throw new ApplicationException("Unable to get meta provider for: " + metafullId);

            switch (moduleType)
            {
                case TableOfContents.Overview:
                    OverViewModule ov = new OverViewModule(metaProvider, doc, Fonts, bookConfig);
                    ImpressumProvider = metaProvider;
                    AppendTables(moduleType, ov.Tables);
                    break;

                case TableOfContents.Catalog:
                    CatVarModule cv = new CatVarModule(metaProvider, doc, Fonts, bookConfig);
                    AppendTables(moduleType, cv.Tables);
                    break;

                case TableOfContents.Questionnaires:
                    QuestionnaireModule questModule = new QuestionnaireModule(metaProvider, doc, Fonts, bookConfig);
                    AppendTables(moduleType, questModule.Tables);
                    break;

                case TableOfContents.DataTables:
                    break;

                case TableOfContents.Codelists:
                    CodelistModule clModule = new CodelistModule(metaProvider, doc, Fonts, bookConfig);
                    AppendTables(moduleType, clModule.Tables);
                    break;

                case TableOfContents.Correspondences:
                    break;
                case TableOfContents.Hierarchies:
                    HierarchyCodelistModule hierarchyModule = new HierarchyCodelistModule(metaProvider, doc, Fonts, bookConfig);
                    AppendTables(moduleType, hierarchyModule.Tables);
                    break;

                default:
                    break;
            }
        }

        private void AppendTables(TableOfContents module, List<PdfBookPTable> list)
        {
            BookTables.AddRange(list);
        }


        private List<PdfPTable> TableInnerSplit(PdfPTable tb, float hRemain, float hPage)
        {
            float hLimit = hRemain;
            List<PdfPTable> tableList = new List<PdfPTable>();
            float height = tb.CalculateHeights(true);
            //if (height > hLimit)
            if (height > hPage)
                {
                PdfPTable tbRow = new PdfPTable(tb);
                tbRow.Rows.Clear();
                tableList.Add(tbRow);
                float curHeight = 0;
                for (int rowIndex = 0; rowIndex < tb.Rows.Count; rowIndex++)
                {
                    PdfPRow row = tb.Rows[rowIndex];
                    curHeight += row.CalculateHeights();
                    if (curHeight < hLimit-25)
                    {
                        tbRow.Rows.Add(row);
                    }
                    else
                    {
                        tbRow = new PdfPTable(tb);
                        tbRow.Rows.Clear();
                        tableList.Add(tbRow);
                        tbRow.Rows.Add(row);
                        curHeight = row.CalculateHeights();
                        hLimit = hPage;
                    }
                }
            }
            else
            {
                tableList.Add(tb);
            }
            return tableList;
        }

        private void AddHeader(string title)
        {
            PdfPTable tbHead = ImpressumModule.HeaderTable(title, TotalWidth, Fonts);
            tbHead.WriteSelectedRows(0, -1, Margins.Left, doc.PageSize.Height - Margins.Top, docWriter.DirectContent);
        }

        private void AddFooter(string text, int page)
        {
            PdfPTable tbFooter = ImpressumModule.FooterTable(text, page, TotalWidth, Fonts);
            tbFooter.WriteSelectedRows(0, -1, Margins.Left, Margins.Bottom, docWriter.DirectContent);
        }

        /// <summary> Distributes CHOP codes tables in Pds pages </summary>
        /// <param name="clTopLevel">the top level used for header comments / splitting</param>
        private void DistributeModule(List<PdfBookPTable> tables, ref int page)
        {
            float curheight = TotalHeight;
            string headerText = "SMS - Statistical Metadata System";
            string footerText = bookConfig.CoverTitle[this.lg];

            foreach (PdfBookPTable bookTbBase in tables)
            {
                PdfPTable tbBase = bookTbBase.Table;
                if (bookTbBase.RequiresNewPage)
                {
                    try
                    {
                        headerText = tbBase.Rows[0].GetCells()[0].Phrase[0].ToString();
                    }
                    catch { }

                    if (curheight != TotalHeight)
                    {
                        curheight = TotalHeight;

                        // Add footer
                        AddFooter(footerText, page++);
                        // next page
                        doc.NewPage();
                        Margins.SwitchMargin();
                        // Add header
                        AddHeader(headerText);
                    }
                    else
                    {
                        AddHeader(headerText);
                    }
                }


                foreach (PdfPTable tb in TableInnerSplit(tbBase, curheight - 
                    bookConfig.Margins.Top / 2, TotalHeight - bookConfig.Margins.Top / 2))
                {
                    float calcHeight = tb.CalculateHeights(true);
                    if (curheight - calcHeight < 25)
                    {
                        curheight = TotalHeight;

                        // Add footer
                        AddFooter(bookConfig.CoverTitle[this.lg], page++);
                        // next page
                        doc.NewPage();
                        Margins.SwitchMargin();
                        // Add header
                        AddHeader(headerText);
                    }

                    //tb.HorizontalAlignment = Element.ALIGN_LEFT;
                    tb.WriteSelectedRows(0, -1, Margins.Left, curheight + Margins.Top/2, docWriter.DirectContent);
                    curheight -= calcHeight;

                }
            }
            // Add final footer
            AddFooter(footerText, page++);
        }

        /// <summary> Distributes CHOP codes tables in Pds pages </summary>
        /// <param name="clTopLevel">the top level used for header comments / splitting</param>
        private void CreateTableEntry(List<PdfBookPTable> tables, ref int page)
        {
            float curheight = TotalHeight;
            string headerText = string.Empty;

            foreach (PdfBookPTable bookTbBase in tables)
            {
                PdfPTable tbBase = bookTbBase.Table;
                if (bookTbBase.RequiresNewPage)
                {
                    try
                    {
                        PdfPCell cell = tbBase.Rows[0].GetCells()[0];
                        if (cell.Phrase != null)
                            headerText = cell.Phrase[0].ToString();  
                    }
                    catch { }

                    if (curheight != TotalHeight)
                    {
                        curheight = TotalHeight;
                        page++;
                        AddToc(headerText, page);
                    }
                    else
                    {
                         AddToc(headerText, page);
                    }
                }


                foreach (PdfPTable tb in TableInnerSplit(tbBase, curheight -
                    Margins.Top / 2, TotalHeight - Margins.Top / 2))
                {
                    float calcHeight = tb.CalculateHeights(true);
                    if (curheight - calcHeight < 25)
                    {
                        curheight = TotalHeight;
                        page++;

                    }

                    curheight -= calcHeight;

                }
            }
        }

        /// <summary>Uses title table of module to create an entry in table of content</summary>
        /// <param name="table">module 1st table</param>
        private void AddToc(string text, int page)
        {
            contentsTable.Add(new TableOfContentsEntry(text, page.ToString()));
        }

    }


}
